setup
rm(list = ls())
library(tidyverse)
library(ggplot2)
library(car)
library(scam)
download data
if (file.exists("openpowerlifting-2020-06-20/openpowerlifting-2020-06-20.csv")){
data.working <- read.csv("openpowerlifting-2020-06-20/openpowerlifting-2020-06-20.csv")
} else {
download.file("https://github.com/sstangl/openpowerlifting-static/raw/gh-pages/openpowerlifting-latest.zip",
"openpowerlifting-latest.zip")
unzip("openpowerlifting-latest.zip")
data.working <- read.csv("openpowerlifting-2020-06-20/openpowerlifting-2020-06-20.csv")
}
summarize data
summary(data.working)
Name Sex Event Equipment Age AgeClass BirthYearClass Division
Length:1979433 Length:1979433 Length:1979433 Length:1979433 Min. : 0.0 Length:1979433 Length:1979433 Length:1979433
Class :character Class :character Class :character Class :character 1st Qu.:21.0 Class :character Class :character Class :character
Mode :character Mode :character Mode :character Mode :character Median :28.0 Mode :character Mode :character Mode :character
Mean :31.4
3rd Qu.:39.5
Max. :98.0
NA's :865345
BodyweightKg WeightClassKg Squat1Kg Squat2Kg Squat3Kg Squat4Kg Best3SquatKg Bench1Kg
Min. : 15.10 Length:1979433 Min. :-555.0 Min. :-580.0 Min. :-600.5 Min. :-550.0 Min. :-477.5 Min. :-502.5
1st Qu.: 67.00 Class :character 1st Qu.: 87.5 1st Qu.: 70.0 1st Qu.:-162.5 1st Qu.:-110.0 1st Qu.: 122.5 1st Qu.: 56.7
Median : 81.92 Mode :character Median : 145.0 Median : 145.0 Median : 110.0 Median : 130.0 Median : 170.0 Median : 105.0
Mean : 84.11 Mean : 113.0 Mean : 92.4 Mean : 31.7 Mean : 68.9 Mean : 175.1 Mean : 83.7
3rd Qu.: 99.00 3rd Qu.: 200.0 3rd Qu.: 205.0 3rd Qu.: 192.5 3rd Qu.: 201.0 3rd Qu.: 220.0 3rd Qu.: 145.0
Max. :260.20 Max. : 555.0 Max. : 577.5 Max. : 560.0 Max. : 505.5 Max. : 580.0 Max. : 467.5
NA's :27067 NA's :1498188 NA's :1503907 NA's :1517871 NA's :1974491 NA's :605421 NA's :1241513
Bench2Kg Bench3Kg Bench4Kg Best3BenchKg Deadlift1Kg Deadlift2Kg Deadlift3Kg Deadlift4Kg
Min. :-575.0 Min. :-575.0 Min. :-500.0 Min. :-522.5 Min. :-461.0 Min. :-470.0 Min. :-587.5 Min. :-461.0
1st Qu.: -50.0 1st Qu.:-137.5 1st Qu.:-128.0 1st Qu.: 75.0 1st Qu.: 125.0 1st Qu.: 115.0 1st Qu.:-207.5 1st Qu.:-117.5
Median : 95.0 Median : -60.0 Median : 75.0 Median : 115.0 Median : 180.0 Median : 177.5 Median : 117.5 Median : 143.0
Mean : 55.2 Mean : -18.2 Mean : 22.2 Mean : 118.2 Mean : 160.9 Mean : 130.1 Mean : 15.2 Mean : 75.4
3rd Qu.: 145.0 3rd Qu.: 117.5 3rd Qu.: 156.2 3rd Qu.: 152.5 3rd Qu.: 225.0 3rd Qu.: 230.0 3rd Qu.: 205.0 3rd Qu.: 208.5
Max. : 487.5 Max. : 478.5 Max. : 487.6 Max. : 488.5 Max. : 450.0 Max. : 460.4 Max. : 457.5 Max. : 440.5
NA's :1250750 NA's :1273724 NA's :1966266 NA's :227920 NA's :1444994 NA's :1455794 NA's :1479964 NA's :1966724
Best3DeadliftKg TotalKg Place Dots Wilks Glossbrenner Goodlift Tested
Min. :-410.0 Min. : 1.0 Length:1979433 Min. : 0.68 Min. : 0.67 Min. : 0.64 Min. : 0.50 Length:1979433
1st Qu.: 140.0 1st Qu.: 217.7 Class :character 1st Qu.:167.30 1st Qu.:166.56 1st Qu.:156.78 1st Qu.: 51.98 Class :character
Median : 188.2 Median : 369.7 Mode :character Median :303.25 Median :302.34 Median :282.75 Median : 63.31 Mode :character
Mean : 189.1 Mean : 388.8 Mean :283.48 Mean :282.47 Mean :266.23 Mean : 63.77
3rd Qu.: 235.0 3rd Qu.: 540.0 3rd Qu.:375.80 3rd Qu.:374.24 3rd Qu.:354.59 3rd Qu.: 75.01
Max. : 460.4 Max. :1407.5 Max. :795.22 Max. :793.33 Max. :756.90 Max. :146.49
NA's :515012 NA's :146611 NA's :163883 NA's :163883 NA's :163883 NA's :305726
Country Federation ParentFederation Date MeetCountry MeetState MeetTown
Length:1979433 Length:1979433 Length:1979433 Length:1979433 Length:1979433 Length:1979433 Length:1979433
Class :character Class :character Class :character Class :character Class :character Class :character Class :character
Mode :character Mode :character Mode :character Mode :character Mode :character Mode :character Mode :character
MeetName
Length:1979433
Class :character
Mode :character
filter data
data.working <- data.working %>%
filter(Event == "SBD",
Equipment == "Raw",
!is.na(Age),
!is.na(BodyweightKg),
!is.na(TotalKg),
ParentFederation == "IPF")
creating features
data.working <- data.working %>%
mutate(AgeBucket = as.factor(case_when(
Age <= 19 ~ "Younger",
Age >= 20 & Age <= 23 ~ "Junior",
Age >= 24 & Age <= 35 ~ "Open",
Age >= 36 ~ "Master",
TRUE ~ "ERROR"))) %>%
mutate(Federation = as.factor(Federation))
# Adding weightclass
data.working$WeightclassKg_Calc <- "error"
data.working[data.working$Sex == "M","WeightclassKg_Calc"] <- data.working %>%
filter(Sex == "M") %>%
transmute(WeightclassKg_Calc = as.character(cut(BodyweightKg,
c(0,53,59,66,74,83,93,105,120,9999))))
data.working[data.working$Sex == "F","WeightclassKg_Calc"] <- data.working %>%
filter(Sex == "F") %>%
transmute(WeightclassKg_Calc = as.character(cut(BodyweightKg,
c(0,43,47,52,57,63,72,84,9999))))
data.working$WeightclassKg_Calc <- as.factor(data.working$WeightclassKg_Calc)
weightclasses <- unique(data.working$WeightclassKg_Calc)
intra-weightclass scam
monotone increasing concave --> bs = "micv"
for (each in weightclasses){
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
4: In readChar(file, size, TRUE) : truncating string with embedded nuls
5: In readChar(file, size, TRUE) : truncating string with embedded nuls
df_tmp <- data.working %>%
filter(WeightclassKg_Calc == each) %>%
select(TotalKg, BodyweightKg)
scam_tmp <- scam(data = df_tmp,
TotalKg ~ s(BodyweightKg, bs = "mpi"))
df_tmp$scam_predict <- predict(scam_tmp)
print(ggplot(df_tmp) +
geom_point(aes(x = BodyweightKg, y = TotalKg)) +
geom_line(aes(x = BodyweightKg, y = scam_predict), color = "blue", size = 2) +
ggtitle(paste(each, ": Raw with GAM")))
data.working[data.working$WeightclassKg_Calc == each, "TotalKg_scam"] <- data.working[data.working$WeightclassKg_Calc == each, "TotalKg"] / df_tmp$scam_predict
print(ggplot(data.working %>% filter(WeightclassKg_Calc == each)) +
geom_point(aes(x = BodyweightKg, y = TotalKg_scam)) +
ggtitle(paste(each, ": Transformed")))
}


































ggplot(data.working %>% filter(Sex == "M")) +
geom_point(aes(x = BodyweightKg, y = TotalKg_scam))

ggplot(data.working %>% filter(Sex == "F")) +
geom_point(aes(x = BodyweightKg, y = TotalKg_scam))

box-cox
box_cox_df <- matrix(ncol = 2,
nrow = length(unique(data.working$WeightclassKg_Calc)))
# dataframe of optimal lambdas
for (x in 1:length(weightclasses)){
tmp <- data.working %>% filter(WeightclassKg_Calc == weightclasses[x])
box_cox_df[x,1] <- as.character(weightclasses[x])
box_cox_df[x,2] <- powerTransform(tmp$TotalKg_scam)$lambda
}
box_cox_df <- as.data.frame(box_cox_df)
colnames(box_cox_df) <- c("WeightclassKg", "lambda")
box_cox_df$WeightclassKg <- as.character(box_cox_df$WeightclassKg)
box_cox_df$lambda <- as.numeric(box_cox_df$lambda)
box_cox_df
# applying box-cox w/ said optimal lambda
data.working$SCORE <- 0
for (each in weightclasses){
x = data.working[data.working$WeightclassKg_Calc == each,"TotalKg_scam"]
data.working[data.working$WeightclassKg_Calc == each,"SCORE"] <- bcPower(x,box_cox_df[box_cox_df$WeightclassKg == each,"lambda"])
}
# divide by mean
for (each in weightclasses){
data.working[data.working$WeightclassKg_Calc == each,"SCORE"] <- scale(data.working[data.working$WeightclassKg_Calc == each,"SCORE"])
}
visualize box cox results
# individual weight classes
for (each in weightclasses){
tmp <- data.working %>%
filter(WeightclassKg_Calc == each) %>%
select(SCORE)
gg <- ggplot(tmp) +
geom_density(aes(x = SCORE)) +
ggtitle(each)
print(gg)
}

















# male / female
for (each in c("M", "F")){
print(ggplot(data.working %>% filter(Sex == each)) +
geom_point((aes(x = BodyweightKg, y = SCORE))) +
ggtitle(paste("Gender =",each)))
}


Top Men & Women
data.working %>%
arrange(desc(SCORE)) %>%
filter(Sex == "M") %>%
filter(row_number() <11)
data.working %>%
arrange(desc(SCORE)) %>%
filter(Sex == "F") %>%
filter(row_number() <11)
NA
LS0tCnRpdGxlOiAib3Blbi1wb3dlcmxpZnRpbmctR0FNIgphdXRob3I6ICJKb2huIE15c2xpbnNraSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQojIHNldHVwCmBgYHtyIHNldHVwLCByZXN1bHRzID0gJ2hpZGUnLCB3YXJuaW5nPUZBTFNFfQpybShsaXN0ID0gbHMoKSkKCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoY2FyKQpsaWJyYXJ5KHNjYW0pCmBgYAoKIyBkb3dubG9hZCBkYXRhCmBgYHtyIGRvd25sb2FkLCByZXN1bHRzID0gJ2hpZGUnLCB3YXJuaW5nPUZBTFNFfQppZiAoZmlsZS5leGlzdHMoIm9wZW5wb3dlcmxpZnRpbmctMjAyMC0wNi0yMC9vcGVucG93ZXJsaWZ0aW5nLTIwMjAtMDYtMjAuY3N2IikpewogIGRhdGEud29ya2luZyA8LSByZWFkLmNzdigib3BlbnBvd2VybGlmdGluZy0yMDIwLTA2LTIwL29wZW5wb3dlcmxpZnRpbmctMjAyMC0wNi0yMC5jc3YiKQp9IGVsc2UgewogIGRvd25sb2FkLmZpbGUoImh0dHBzOi8vZ2l0aHViLmNvbS9zc3RhbmdsL29wZW5wb3dlcmxpZnRpbmctc3RhdGljL3Jhdy9naC1wYWdlcy9vcGVucG93ZXJsaWZ0aW5nLWxhdGVzdC56aXAiLAogICAgICAgICAgICAgICJvcGVucG93ZXJsaWZ0aW5nLWxhdGVzdC56aXAiKQogIHVuemlwKCJvcGVucG93ZXJsaWZ0aW5nLWxhdGVzdC56aXAiKQogIAogIGRhdGEud29ya2luZyA8LSByZWFkLmNzdigib3BlbnBvd2VybGlmdGluZy0yMDIwLTA2LTIwL29wZW5wb3dlcmxpZnRpbmctMjAyMC0wNi0yMC5jc3YiKQp9CmBgYAoKIyBzdW1tYXJpemUgZGF0YQpgYGB7ciBnbGltcHNlfQpzdW1tYXJ5KGRhdGEud29ya2luZykKYGBgCgojIGZpbHRlciBkYXRhCmBgYHtyIGZpbHRlcn0KZGF0YS53b3JraW5nIDwtIGRhdGEud29ya2luZyAlPiUKICBmaWx0ZXIoRXZlbnQgPT0gIlNCRCIsCiAgICAgICAgIEVxdWlwbWVudCA9PSAiUmF3IiwKICAgICAgICAgIWlzLm5hKEFnZSksCiAgICAgICAgICFpcy5uYShCb2R5d2VpZ2h0S2cpLAogICAgICAgICAhaXMubmEoVG90YWxLZyksCiAgICAgICAgIFBhcmVudEZlZGVyYXRpb24gPT0gIklQRiIpCmBgYAoKIyBjcmVhdGluZyBmZWF0dXJlcwpgYGB7ciBmZWF0dXJlc30KZGF0YS53b3JraW5nIDwtIGRhdGEud29ya2luZyAlPiUKICBtdXRhdGUoQWdlQnVja2V0ID0gYXMuZmFjdG9yKGNhc2Vfd2hlbigKICAgICAgICAgQWdlIDw9IDE5IH4gIllvdW5nZXIiLAogICAgICAgICBBZ2UgPj0gMjAgJiBBZ2UgPD0gMjMgfiAiSnVuaW9yIiwKICAgICAgICAgQWdlID49IDI0ICYgQWdlIDw9IDM1IH4gIk9wZW4iLAogICAgICAgICBBZ2UgPj0gMzYgfiAiTWFzdGVyIiwKICAgICAgICAgVFJVRSB+ICJFUlJPUiIpKSkgJT4lCiAgbXV0YXRlKEZlZGVyYXRpb24gPSBhcy5mYWN0b3IoRmVkZXJhdGlvbikpCgoKIyBBZGRpbmcgd2VpZ2h0Y2xhc3MKZGF0YS53b3JraW5nJFdlaWdodGNsYXNzS2dfQ2FsYyA8LSAiZXJyb3IiCgpkYXRhLndvcmtpbmdbZGF0YS53b3JraW5nJFNleCA9PSAiTSIsIldlaWdodGNsYXNzS2dfQ2FsYyJdIDwtIGRhdGEud29ya2luZyAlPiUgCiAgZmlsdGVyKFNleCA9PSAiTSIpICU+JQogIHRyYW5zbXV0ZShXZWlnaHRjbGFzc0tnX0NhbGMgPSBhcy5jaGFyYWN0ZXIoY3V0KEJvZHl3ZWlnaHRLZywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoMCw1Myw1OSw2Niw3NCw4Myw5MywxMDUsMTIwLDk5OTkpKSkpCgpkYXRhLndvcmtpbmdbZGF0YS53b3JraW5nJFNleCA9PSAiRiIsIldlaWdodGNsYXNzS2dfQ2FsYyJdIDwtIGRhdGEud29ya2luZyAlPiUgCiAgZmlsdGVyKFNleCA9PSAiRiIpICU+JQogIHRyYW5zbXV0ZShXZWlnaHRjbGFzc0tnX0NhbGMgPSBhcy5jaGFyYWN0ZXIoY3V0KEJvZHl3ZWlnaHRLZywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoMCw0Myw0Nyw1Miw1Nyw2Myw3Miw4NCw5OTk5KSkpKQoKZGF0YS53b3JraW5nJFdlaWdodGNsYXNzS2dfQ2FsYyA8LSBhcy5mYWN0b3IoZGF0YS53b3JraW5nJFdlaWdodGNsYXNzS2dfQ2FsYykKYGBgCgpgYGB7cn0Kd2VpZ2h0Y2xhc3NlcyA8LSB1bmlxdWUoZGF0YS53b3JraW5nJFdlaWdodGNsYXNzS2dfQ2FsYykKYGBgCgoKIyBpbnRyYS13ZWlnaHRjbGFzcyBzY2FtCiMjIG1vbm90b25lIGluY3JlYXNpbmcgY29uY2F2ZSAtLT4gYnMgPSAibWljdiIKYGBge3J9CmZvciAoZWFjaCBpbiB3ZWlnaHRjbGFzc2VzKXsKICAKICBkZl90bXAgPC0gZGF0YS53b3JraW5nICU+JQogICAgZmlsdGVyKFdlaWdodGNsYXNzS2dfQ2FsYyA9PSBlYWNoKSAlPiUKICAgIHNlbGVjdChUb3RhbEtnLCBCb2R5d2VpZ2h0S2cpCiAgCiAgc2NhbV90bXAgPC0gc2NhbShkYXRhID0gZGZfdG1wLAogICAgICAgICAgICAgICAgICAgVG90YWxLZyB+IHMoQm9keXdlaWdodEtnLCBicyA9ICJtcGkiKSkKICAKICBkZl90bXAkc2NhbV9wcmVkaWN0IDwtIHByZWRpY3Qoc2NhbV90bXApCiAgCiAgcHJpbnQoZ2dwbG90KGRmX3RtcCkgKwogICAgZ2VvbV9wb2ludChhZXMoeCA9IEJvZHl3ZWlnaHRLZywgeSA9IFRvdGFsS2cpKSArCiAgICBnZW9tX2xpbmUoYWVzKHggPSBCb2R5d2VpZ2h0S2csIHkgPSBzY2FtX3ByZWRpY3QpLCBjb2xvciA9ICJibHVlIiwgc2l6ZSA9IDIpICsKICAgIGdndGl0bGUocGFzdGUoZWFjaCwgIjogUmF3IHdpdGggR0FNIikpKQogIAogIGRhdGEud29ya2luZ1tkYXRhLndvcmtpbmckV2VpZ2h0Y2xhc3NLZ19DYWxjID09IGVhY2gsICJUb3RhbEtnX3NjYW0iXSA8LSBkYXRhLndvcmtpbmdbZGF0YS53b3JraW5nJFdlaWdodGNsYXNzS2dfQ2FsYyA9PSBlYWNoLCAiVG90YWxLZyJdIC8gZGZfdG1wJHNjYW1fcHJlZGljdAogIAogIHByaW50KGdncGxvdChkYXRhLndvcmtpbmcgJT4lIGZpbHRlcihXZWlnaHRjbGFzc0tnX0NhbGMgPT0gZWFjaCkpICsKICAgIGdlb21fcG9pbnQoYWVzKHggPSBCb2R5d2VpZ2h0S2csIHkgPSBUb3RhbEtnX3NjYW0pKSArCiAgICBnZ3RpdGxlKHBhc3RlKGVhY2gsICI6IFRyYW5zZm9ybWVkIikpKQp9CgoKZ2dwbG90KGRhdGEud29ya2luZyAlPiUgZmlsdGVyKFNleCA9PSAiTSIpKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IEJvZHl3ZWlnaHRLZywgeSA9IFRvdGFsS2dfc2NhbSkpCgpnZ3Bsb3QoZGF0YS53b3JraW5nICU+JSBmaWx0ZXIoU2V4ID09ICJGIikpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gQm9keXdlaWdodEtnLCB5ID0gVG90YWxLZ19zY2FtKSkKYGBgCgojIGJveC1jb3gKYGBge3IgYm94LWNveC10cmFuc2Zvcm19CmJveF9jb3hfZGYgPC0gbWF0cml4KG5jb2wgPSAyLAogICAgICAgICAgICAgICAgICAgICBucm93ID0gbGVuZ3RoKHVuaXF1ZShkYXRhLndvcmtpbmckV2VpZ2h0Y2xhc3NLZ19DYWxjKSkpCgojIGRhdGFmcmFtZSBvZiBvcHRpbWFsIGxhbWJkYXMKZm9yICh4IGluIDE6bGVuZ3RoKHdlaWdodGNsYXNzZXMpKXsKICB0bXAgPC0gZGF0YS53b3JraW5nICU+JSBmaWx0ZXIoV2VpZ2h0Y2xhc3NLZ19DYWxjID09IHdlaWdodGNsYXNzZXNbeF0pCiAgYm94X2NveF9kZlt4LDFdIDwtIGFzLmNoYXJhY3Rlcih3ZWlnaHRjbGFzc2VzW3hdKQogIGJveF9jb3hfZGZbeCwyXSA8LSBwb3dlclRyYW5zZm9ybSh0bXAkVG90YWxLZ19zY2FtKSRsYW1iZGEKfQoKYm94X2NveF9kZiA8LSBhcy5kYXRhLmZyYW1lKGJveF9jb3hfZGYpCmNvbG5hbWVzKGJveF9jb3hfZGYpIDwtIGMoIldlaWdodGNsYXNzS2ciLCAibGFtYmRhIikKYm94X2NveF9kZiRXZWlnaHRjbGFzc0tnIDwtIGFzLmNoYXJhY3Rlcihib3hfY294X2RmJFdlaWdodGNsYXNzS2cpCmJveF9jb3hfZGYkbGFtYmRhIDwtIGFzLm51bWVyaWMoYm94X2NveF9kZiRsYW1iZGEpCmJveF9jb3hfZGYKCiMgYXBwbHlpbmcgYm94LWNveCB3LyBzYWlkIG9wdGltYWwgbGFtYmRhCmRhdGEud29ya2luZyRTQ09SRSA8LSAwCmZvciAoZWFjaCBpbiB3ZWlnaHRjbGFzc2VzKXsKICB4ID0gZGF0YS53b3JraW5nW2RhdGEud29ya2luZyRXZWlnaHRjbGFzc0tnX0NhbGMgPT0gZWFjaCwiVG90YWxLZ19zY2FtIl0KICBkYXRhLndvcmtpbmdbZGF0YS53b3JraW5nJFdlaWdodGNsYXNzS2dfQ2FsYyA9PSBlYWNoLCJTQ09SRSJdIDwtIGJjUG93ZXIoeCxib3hfY294X2RmW2JveF9jb3hfZGYkV2VpZ2h0Y2xhc3NLZyA9PSBlYWNoLCJsYW1iZGEiXSkKfQoKCiMgZGl2aWRlIGJ5IG1lYW4KZm9yIChlYWNoIGluIHdlaWdodGNsYXNzZXMpewogIGRhdGEud29ya2luZ1tkYXRhLndvcmtpbmckV2VpZ2h0Y2xhc3NLZ19DYWxjID09IGVhY2gsIlNDT1JFIl0gPC0gc2NhbGUoZGF0YS53b3JraW5nW2RhdGEud29ya2luZyRXZWlnaHRjbGFzc0tnX0NhbGMgPT0gZWFjaCwiU0NPUkUiXSkKfQpgYGAKCgojIHZpc3VhbGl6ZSBib3ggY294IHJlc3VsdHMKYGBge3Igdml6LWJveC1jb3h9CiMgaW5kaXZpZHVhbCB3ZWlnaHQgY2xhc3Nlcwpmb3IgKGVhY2ggaW4gd2VpZ2h0Y2xhc3Nlcyl7CiAgdG1wIDwtIGRhdGEud29ya2luZyAlPiUKICAgIGZpbHRlcihXZWlnaHRjbGFzc0tnX0NhbGMgPT0gZWFjaCkgJT4lCiAgICBzZWxlY3QoU0NPUkUpCiAgCiAgZ2cgPC0gZ2dwbG90KHRtcCkgKwogICAgZ2VvbV9kZW5zaXR5KGFlcyh4ID0gU0NPUkUpKSArCiAgICBnZ3RpdGxlKGVhY2gpCiAgCiAgcHJpbnQoZ2cpCn0KCiMgbWFsZSAvIGZlbWFsZQpmb3IgKGVhY2ggaW4gYygiTSIsICJGIikpewogIHByaW50KGdncGxvdChkYXRhLndvcmtpbmcgJT4lIGZpbHRlcihTZXggPT0gZWFjaCkpICsKICAgIGdlb21fcG9pbnQoKGFlcyh4ID0gQm9keXdlaWdodEtnLCB5ID0gU0NPUkUpKSkgKwogICAgZ2d0aXRsZShwYXN0ZSgiR2VuZGVyID0iLGVhY2gpKSkKfQoKYGBgCiMgVG9wIE1lbiAmIFdvbWVuCmBgYHtyfQpkYXRhLndvcmtpbmcgJT4lCiAgYXJyYW5nZShkZXNjKFNDT1JFKSkgJT4lCiAgZmlsdGVyKFNleCA9PSAiTSIpICU+JQogIGZpbHRlcihyb3dfbnVtYmVyKCkgPDExKQoKCmRhdGEud29ya2luZyAlPiUKICBhcnJhbmdlKGRlc2MoU0NPUkUpKSAlPiUKICBmaWx0ZXIoU2V4ID09ICJGIikgJT4lCiAgZmlsdGVyKHJvd19udW1iZXIoKSA8MTEpCmBgYAoKCgoK